package org.teamh.artificialIntelligence;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;

import org.teamh.connection.json.command.JsonSender;
import org.teamh.logic.datamodel.Cell;
import org.teamh.logic.datamodel.Creep;
import org.teamh.logic.datamodel.Tower;
import org.teamh.logic.datamodel.TowerDescription;
import org.teamh.logic.datamodel.User;
import org.teamh.logic.datamodel.UserAssets;
import org.teamh.logic.datamodel.Waypoint;

public class Defender {

	private static final int BUILD_NEW = 1;
	private static final int UPGRADE = 2;
	private static final int SELL_AND_REBUILD = 3;

	private static final String BASIC_TOWER = "Basictower";
	private static final String SPEED_TOWER = "Speedtower";
	private static final String SPLASH_TOWER = "Splashtower";
	private static final String ROCKET_TOWER = "Rockettower";
	private static final String SLOW_TOWER = "Slowtower";
	private static final String ULTIMATE_TOWER = "Ultimatetower";
	
	private static final float BASIC_TOWER_RANGE = 1.75f;
	private static final float SPEED_TOWER_RANGE = 2.5f;
	private static final float SPLASH_TOWER_RANGE = 2.0f;
	private static final float ROCKET_TOWER_RANGE = 2.5f;

	private static boolean noMoreDefending = false;
	
	private Defender() {
	}

	public static void defend() {
		if (noMoreDefending) {
			return;
		}

		Creep lastCreep = getLastCreepOfTheMap();

		if (lastCreep != null) {
			boolean allCreepsKilled = false;

			while (!allCreepsKilled && AIInterfaceManager.isRunning()) {
				////System.out.println("still creeps alive");
				while(AIInterfaceManager.isRunning() && AIInterfaceManager.user.getUserAssets().getBalanceForSimulation() < 50) {
					AIInterfaceManager.waits(20);
					////System.out.println("wait 4 money");
				}
				
				////System.out.println("--> start defensive simulation");
				
				ArrayList<SimulationResult> simulations = getListForSimulation(
						getTowersAtCreep(lastCreep, Initiator.get(BASIC_TOWER), BASIC_TOWER_RANGE),
						getTowersAtCreep(lastCreep, Initiator.get(SPEED_TOWER), SPEED_TOWER_RANGE),
						getTowersAtCreep(lastCreep, Initiator.get(SPLASH_TOWER), SPLASH_TOWER_RANGE),
						getTowersAtCreep(lastCreep, Initiator.get(ROCKET_TOWER), ROCKET_TOWER_RANGE));
//				ArrayList<SimulationResult> simulations = getListForSimulation(
//						basicTower, speedTower, splashTower, rocketTower);

				////System.out.println("1: length: " + simulations.size());
				
				// 9: if list is empty widen the list to the whole map and go
				// back get the simulation resutls for the whole map
				if (simulations.size() == 0) {
					simulations = getListForSimulation(
							Initiator.get(BASIC_TOWER),
							Initiator.get(SPEED_TOWER),
							Initiator.get(SPLASH_TOWER),
							Initiator.get(ROCKET_TOWER));
				}
				
				////System.out.println("2: length: " + simulations.size());
				
				// 10: if list is empty on the whole map, set the ultimate
				// tower, as the only tower to simulate
				if (simulations.size() == 0) {
					for (Cell c : Initiator.get(ULTIMATE_TOWER)) {
						if (!c.isHasTowerForSimulation()
								&& AIInterfaceManager.user.getUserAssets().getBalanceForSimulation() >= getTowerDescription(ULTIMATE_TOWER, 1).getPrice()) {
							simulations.add(new SimulationResult()
									.withAction(BUILD_NEW).withCell(c)
									.withNewTower(ULTIMATE_TOWER));
							break;
						}
					}
				}

				// 11: if list is empty, find ultimate tower to upgrade and add
				// it to the list
				if (simulations.size() == 0) {
					for (Cell c : Initiator.get(ULTIMATE_TOWER)) {
						if (c.getTower() != null
								&& c.getTower().getTowerDescription() != null
								&& c.getTower().getTowerDescription()
										.getLevel() < 2
								&& AIInterfaceManager.user.getUserAssets().getBalanceForSimulation() >= getTowerDescription(ULTIMATE_TOWER, 2).getPrice()) {
							simulations.add(new SimulationResult().withAction(
									UPGRADE).withCell(c));
							break;
						}
					}
				}

				// 12: if list is not empty, start simulating for any situation
				// of
				// the list
				if (simulations.size() > 0) {
					// simulate for results
					for (SimulationResult s : simulations) {
						simulate(s);
					}

					// 13: select best solution and build it.
					SimulationResult bestSolution = simulations.get(0);
					for (SimulationResult r : simulations) {
						if (r.creepsSurvived < bestSolution.creepsSurvived) {
							bestSolution = r;
						} else if(r.creepsSurvived == bestSolution.creepsSurvived) {
							if(r.totalHealth < bestSolution.totalHealth) {
								bestSolution = r;
							}
						}
					}
					
					//build the selected solution
					buildSolution(bestSolution);

					// 14: if not all creeps could be killed with the best
					// solution, go
					// back to step 1
					if (bestSolution.creepsSurvived <= 0) {
						allCreepsKilled = true;
					}
				} else {
					// 15: there can no more tower be build. defender is
					// senseless now
					if(AIInterfaceManager.user.getUserAssets().getBalanceForSimulation() > 100000) {
						noMoreDefending = true;
						break;
					}
				}
			}
		}
		
		////System.out.println("****************************************************");
		////System.out.println("balance left: " + AIInterfaceManager.user.getUserAssets().getBalanceForSimulation());
		////System.out.println("****************************************************");
		
		//Build corresponding slow tower if possible
		for(Cell c: AIInterfaceManager.user.getUserAssets().getTile().getMap().getCells().values()) {
			if(c.isHasTowerForSimulation()) {
				Cell slowTowerCell = c.getNearestSlowtowerCell();
				if(slowTowerCell != null) {
					if(slowTowerCell.isHasTowerForSimulation()) {
						////System.out.println("cell is full");
						if(slowTowerCell.getTower() != null) {
							if(!slowTowerCell.getTower().isUpgrading() && !slowTowerCell.getTower().isBuilding() && slowTowerCell.getTower().getLevel() < 4) {
								//upgrade slow tower if possible
								TowerDescription t = getTowerDescription(SLOW_TOWER, slowTowerCell.getTower().getLevel() + 1);
								if(t.getPrice() <= AIInterfaceManager.user.getUserAssets().getBalanceForSimulation()) {
									slowTowerCell.getTower().setUpgrading(true);
									JsonSender.upgradeTower(slowTowerCell.getTower().getID(), (slowTowerCell.getTower().getLevel() + 1) + "");
									////System.out.println("upgrade slow tower");
									AIInterfaceManager.user.getUserAssets().removeFromBalanceForSimulation(t.getPrice());
								}
							}
						}
					} else {
						if(AIInterfaceManager.user.getUserAssets().getBalanceForSimulation() >= 100) {
							JsonSender.createTower(SLOW_TOWER.replaceAll("tower", ""), slowTowerCell.getID());
							slowTowerCell.setHasTowerForSimulation(true);
							////System.out.println("build slow tower");
							AIInterfaceManager.user.getUserAssets().removeFromBalanceForSimulation(100);
						}
						//build new slow tower if possible
					}
				}
			}
		}
	}

	private static ArrayList<Cell> getTowersAtCreep(Creep lastCreep,
			ArrayList<Cell> towers, float range) {
		ArrayList<Cell> newTowerCells = new ArrayList<Cell>();
		for(Cell c : towers) {
			for(Waypoint w : AIInterfaceManager.user.getUserAssets().getTile().getMap().getWaypoints().values()) {
				if(lastCreep.getCurrentWaypoint().getIndex() + 3 < w.getIndex() && getDistance(w.getCell(), c) <= range * range) {
					newTowerCells.add(c);
				}
			}
		}
		
		return newTowerCells;
	}
	
	private static void simulate(SimulationResult result) {
		switch (result.action) {
		case BUILD_NEW:
			////System.out.println("simulate for build new");
			simulateForBuildNew(result);
			break;
		case UPGRADE:
			////System.out.println("simulate for upgrade");
			simulateForUpgrade(result);
			break;
		case SELL_AND_REBUILD:
			////System.out.println("simulate for sell and upgrade");
			simulateForSellingAndBuilding(result);
			break;
		}
	}

	private static void simulateForBuildNew(SimulationResult result) {
		Tower t = new Tower(Math.random() + "", null);
		t.setStrategyForSimulation("CLOSEST");
		t.setTowerDescriptionForSimulation(getTowerDescription(result.newTower, 1));
		t.setLevelForSimulation(1);
		t.setChangedBySimulation(true);
		t.setCell(result.cell);
		
		AIInterfaceManager.user.getUserAssets().addTower(t);
		
		Simulator.simulate(AIInterfaceManager.user);
		
		int survived = 0;
		int health = 0;
		try {
			for (Creep c : Simulator.currentCreeps) {
				if (c.getHealthForSimulation() > 0) {
					survived++;
					health += c.getHealthForSimulation();
				}
			}
		} catch (ConcurrentModificationException e) {
			e.printStackTrace();
		}
		
		result.creepsSurvived = survived;
		result.totalHealth = health;
		
		t.setCell(null);
		AIInterfaceManager.user.getUserAssets().removeTower(t);
	}

	private static void simulateForUpgrade(SimulationResult result) {
		Tower t = result.cell.getTower();
		t.setStrategyForSimulation("CLOSEST");
		t.setTowerDescriptionForSimulation(getTowerDescription(t.getTowerDescription().getName(), t.getTowerDescription().getLevel() + 1));
		t.setLevelForSimulation(1);
		t.setChangedBySimulation(true);
		
		Simulator.simulate(AIInterfaceManager.user);
		
		int survived = 0;
		int health = 0;
		try {
			for (Creep c : Simulator.currentCreeps) {
				if (c.getHealthForSimulation() > 0) {
					survived++;
					health += c.getHealthForSimulation();
				}
			}
		} catch (ConcurrentModificationException e) {
			e.printStackTrace();
		}
		
		result.creepsSurvived = survived;
		result.totalHealth = health;
	}

	private static void simulateForSellingAndBuilding(SimulationResult result) {
		Tower oldTower = result.cell.getTower();
		
		AIInterfaceManager.user.getUserAssets().removeTower(oldTower);
		
		simulateForBuildNew(result);
		
		AIInterfaceManager.user.getUserAssets().addTower(oldTower);
		oldTower.setCell(result.cell);
	}
	
	private static void buildSolution(SimulationResult result) {
		switch (result.action) {
		case BUILD_NEW:
			buildNewTower(result);
			break;
		case UPGRADE:
			buildUpgrade(result);
			break;
		case SELL_AND_REBUILD:
			buildSellAndBuild(result);
			break;
		}
	}
	
	private static void buildNewTower(final SimulationResult result) {
		////System.out.println("build new tower");
		AIInterfaceManager.user.getUserAssets().removeFromBalanceForSimulation(getTowerDescription(result.newTower, 1).getPrice());
		result.cell.setHasTowerForSimulation(true);
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				////System.out.println("--> build new tower: " + result.newTower);
				JsonSender.createTower(result.buildName, result.cell.getID());
				
				while(result.cell.getTower() == null) {
					AIInterfaceManager.waits(20);
				}
				
				while(result.cell.getTower().isBuilding()) {
					AIInterfaceManager.waits(20);
				}
				
				JsonSender.changeTowerStrategy(result.cell.getTower().getID(), getPreferredStrategy(result.newTower));
			}
		}).start();
	}
	
	private static void buildUpgrade(SimulationResult result) {
		////System.out.println("--> upgrade: " + result.newTower);
		int level = result.cell.getTower().getTowerDescription().getLevel();
		String tower = result.cell.getTower().getID();
		result.cell.getTower().setUpgrading(true);
		JsonSender.upgradeTower(tower, (level + 1) + "");
		
		////System.out.println("upgrade tower");
		AIInterfaceManager.user.getUserAssets().removeFromBalanceForSimulation(getTowerDescription(result.newTower, level + 1).getPrice());
	}
	
	private static void buildSellAndBuild(final SimulationResult result) {
		////System.out.println("--> sell and build new: " + result.newTower);
		result.cell.getTower().setSelling(true);
		////System.out.println("sell and build tower");
		AIInterfaceManager.user.getUserAssets().removeFromBalanceForSimulation(getTowerDescription(result.newTower, 1).getPrice());
		new Thread(new Runnable() {
			@Override
			public void run() {
				//sell tower
				////System.out.println("$1: sell tower");
				JsonSender.sellTower(result.cell.getTower().getID());
				
				//wait until selling
				////System.out.println("$2: wait for empty cell");
				while(result.cell.getTower() != null && !result.cell.getTower().isSelling()) {
					AIInterfaceManager.waits(20);
				}
				
				////System.out.println("$3: wait for empty cell");
				while(result.cell.getTower() != null && result.cell.getTower().isSelling()) {
					AIInterfaceManager.waits(20);
				}
				
				////System.out.println("$4: wait for empty cell");
				while(result.cell.getTower() != null) {
					AIInterfaceManager.waits(20);
				}
				
				////System.out.println("$6: ");
				while(result.cell.getTower() == null) {
					AIInterfaceManager.waits(300);
					JsonSender.createTower(result.buildName, result.cell.getID());
				}
				
				////System.out.println("$8: ");
				while(result.cell.getTower().isBuilding()) {
					AIInterfaceManager.waits(20);
				}
				
				//set strategy
				////System.out.println("9: ");
				JsonSender.changeTowerStrategy(result.cell.getTower().getID(), getPreferredStrategy(result.newTower));
				////System.out.println("10: ");
			}
		}).start();
	}
	
	private static String getPreferredStrategy(String name) {
		if(name == SLOW_TOWER || name == ULTIMATE_TOWER) {
			return "STRONGEST";
		} else {
			return "CLOSEST";
		}
	}

	private static ArrayList<SimulationResult> getListForSimulation(
			ArrayList<Cell> basicTower, ArrayList<Cell> speedTower,
			ArrayList<Cell> splashTower, ArrayList<Cell> rocketTower) {
		////System.out.println("start getting list for simulation");
		ArrayList<SimulationResult> simulations = new ArrayList<Defender.SimulationResult>();

		int balance = AIInterfaceManager.user.getUserAssets()
				.getBalanceForSimulation();
		////System.out.println("balance: " + balance);
		// 1: check, if basic or speed
		ArrayList<Cell> singleShotList = null;
		String singleShot = null;
		if (balance >= 1000) {
			singleShot = SPEED_TOWER;
			singleShotList = speedTower;
		} else if (balance >= 50) {
			singleShot = BASIC_TOWER;
			singleShotList = basicTower;
		}
		
		////System.out.println("selected single shot tower: " + singleShot);
		
		// 2: check, if splash or rocket
		ArrayList<Cell> splashShotList = null;
		String splashShot = null;
		if (balance >= 1000) {
			splashShotList = rocketTower;
			splashShot = ROCKET_TOWER;
		} else if (balance >= 250) {
			splashShotList = splashTower;
			splashShot = SPLASH_TOWER;
		}
		
		////System.out.println("selected splash shot tower: " + splashShot);

		// 3: find basic/speed tower to build
		if (singleShotList != null) {
			for (Cell c : singleShotList) {
				if (c.isHasTowerForSimulation()) {
					if (c.getTower() != null
							&& !c.getTower().isBuilding()
							&& !c.getTower().isSelling()
							&& !c.getTower().isUpgrading()
							&& c.getTower().getTowerDescription() != null
							&& !c.getTower().getTowerDescription().getName()
									.equalsIgnoreCase(singleShot)
							&& c.getTower().getTowerDescription().getName()
									.equalsIgnoreCase(BASIC_TOWER)) {
						simulations.add(new SimulationResult()
								.withAction(SELL_AND_REBUILD).withCell(c)
								.withNewTower(singleShot));
						break;
					}
				} else {
					simulations.add(new SimulationResult()
							.withAction(BUILD_NEW).withCell(c)
							.withNewTower(singleShot));
					break;
				}
			}
		}
		// 4: find speed tower to upgrade
		boolean speedTowerFound = false;
		for (Cell c : speedTower) {
			// check, if tower exists, and find out, if tower description is
			// set correctly and if the tower is not currently build or
			// upgraded
			if (c.getTower() != null
					&& !c.getTower().isBuilding()
					&& !c.getTower().isSelling()
					&& !c.getTower().isUpgrading()
					&& c.getTower().getTowerDescription() != null
					&& c.getTower().getTowerDescription().getName()
							.equals(SPEED_TOWER)) {
				// tower description of the next level
				TowerDescription towerDescription = getTowerDescription(c
						.getTower().getTowerDescription().getName(), c
						.getTower().getTowerDescription().getLevel() + 1);
				if (towerDescription != null
						&& towerDescription.getPrice() <= balance) {
					simulations.add(new SimulationResult().withAction(UPGRADE)
							.withCell(c).withNewTower(SPEED_TOWER));
					speedTowerFound = true;
					break;
				}
			}
		}
		// 5: if no speed tower to upgrade, find basic tower to upgrade
		if (!speedTowerFound) {
			////System.out.println("has no speed tower found");
			for (Cell c : basicTower) {
				if (c.getTower() != null
						&& !c.getTower().isBuilding()
						&& !c.getTower().isSelling()
						&& !c.getTower().isUpgrading()
						&& c.getTower().getTowerDescription() != null
						&& c.getTower().getTowerDescription().getName()
								.equals(BASIC_TOWER)) {
					TowerDescription towerDescription = getTowerDescription(c
							.getTower().getTowerDescription().getName(), c
							.getTower().getTowerDescription().getLevel() + 1);
				
					if (towerDescription != null
							&& towerDescription.getPrice() <= balance) {
						simulations.add(new SimulationResult().withAction(
								UPGRADE).withCell(c).withNewTower(BASIC_TOWER));
						break;
					}
				}
			}
		}

		// find splash/rocket tower to build
		if (splashShotList != null) {
			for (Cell c : splashShotList) {
				if (c.isHasTowerForSimulation()) {
					if (c.getTower() != null
							&& !c.getTower().isBuilding()
							&& !c.getTower().isSelling()
							&& !c.getTower().isUpgrading()
							&& c.getTower().getTowerDescription() != null
							&& !c.getTower().getTowerDescription().getName()
									.equalsIgnoreCase(splashShot)
							&& c.getTower().getTowerDescription().getName()
									.equalsIgnoreCase(SPLASH_TOWER)) {
						simulations.add(new SimulationResult()
								.withAction(SELL_AND_REBUILD).withCell(c)
								.withNewTower(splashShot));
						break;
					}
				} else {
					simulations.add(new SimulationResult()
							.withAction(BUILD_NEW).withCell(c)
							.withNewTower(splashShot));
					break;
				}
			}
		}

		// 7: find rocket tower to upgrade
		boolean rocketTowerFound = false;
		for (Cell c : rocketTower) {
			// check, if tower exists, and find out, if tower description is
			// set correctly and if the tower is not currently build or
			// upgraded
			if (c.getTower() != null
					&& !c.getTower().isBuilding()
					&& !c.getTower().isSelling()
					&& !c.getTower().isUpgrading()
					&& c.getTower().getTowerDescription() != null
					&& c.getTower().getTowerDescription().getName()
							.equals(ROCKET_TOWER)) {
				// tower description of the next level
				TowerDescription towerDescription = getTowerDescription(c
						.getTower().getTowerDescription().getName(), c
						.getTower().getTowerDescription().getLevel() + 1);
				if (towerDescription != null
						&& towerDescription.getPrice() <= balance) {
					simulations.add(new SimulationResult().withAction(UPGRADE)
							.withCell(c).withNewTower(ROCKET_TOWER));
					rocketTowerFound = true;
					break;
				}
			}
		}
		// 8: if no rocket tower to upgrade, find splash tower to upgrade
		if (!rocketTowerFound) {
			for (Cell c : splashTower) {
				if (c.getTower() != null
						&& !c.getTower().isBuilding()
						&& !c.getTower().isSelling()
						&& !c.getTower().isUpgrading()
						&& c.getTower().getTowerDescription() != null
						&& c.getTower().getTowerDescription().getName()
								.equals(SPLASH_TOWER)) {
					TowerDescription towerDescription = getTowerDescription(c
							.getTower().getTowerDescription().getName(), c
							.getTower().getTowerDescription().getLevel() + 1);
					if (towerDescription != null
							&& towerDescription.getPrice() <= balance) {
						simulations.add(new SimulationResult().withAction(
								UPGRADE).withCell(c).withNewTower(SPLASH_TOWER));
						break;
					}
				}
			}
		}

		return simulations;
	}

	private static Creep getLastCreepOfTheMap() {
		Creep lastCreep = null;

		for (Waypoint w : AIInterfaceManager.user.getUserAssets().getTile()
				.getMap().getWaypoints().values()) {
			for (Creep c : w.getCell().getCreeps().values()) {
				if (c.getHealthForSimulation() > 0) {
					if (lastCreep != null) {
						if(c.getCurrentWaypoint() != null && lastCreep.getCurrentWaypoint() != null) {
							if (c.getCurrentWaypoint().getIndex() > lastCreep
									.getCurrentWaypoint().getIndex()) {
								lastCreep = c;
							} else if (c.getCurrentWaypoint().getIndex() == lastCreep
									.getCurrentWaypoint().getIndex()) {
								if (c.getWalkedDistance() > lastCreep
										.getWalkedDistance()) {
									lastCreep = c;
								}
							}
						}
					} else {
						lastCreep = c;
					}
				}
			}
		}

		return lastCreep;
	}

	private static TowerDescription getTowerDescription(String name, int level) {
		for (TowerDescription t : AIInterfaceManager.datamodelManager
				.getTowerDescriptions().values()) {
			if (t.getName().equalsIgnoreCase(name) && t.getLevel() == level) {
				return t;
			}
		}

		return null;
	}

	public static boolean checkForDefending() {
		if (noMoreDefending) {
			return false;
		}

		////System.out.println("start check for defending");

		User user = AIInterfaceManager.user;

		Simulator.simulate(user);

		int survived = 0;
		try {
			for (Waypoint w : user.getUserAssets().getTile().getMap()
					.getWaypoints().values()) {
				for (Creep c : w.getCell().getCreeps().values()) {
					if (c.getHealthForSimulation() > 0) {
						survived++;
					}
					////System.out.println(c.getID() + ": "
//							+ c.getHealthForSimulation() + " / "
//							+ c.getHealth());
				}
			}
		} catch (ConcurrentModificationException e) {
			e.printStackTrace();
		}

		if (survived > 0) {
			//System.out.println("decided to defend");
			return true;
		}

		//System.out.println("decided not to defend");
		return false;
	}

	public static void test() {
		User user = null;
		for (UserAssets u : AIInterfaceManager.datamodelManager.getUserAssets()
				.values()) {
			if (u.getUser() != AIInterfaceManager.user && u.getTile() != null) {
				user = u.getUser();
			}
		}

		Simulator.simulate(user);

		int survived = 0;
		try {
			for (Waypoint w : user.getUserAssets().getTile().getMap()
					.getWaypoints().values()) {
				for (Creep c : w.getCell().getCreeps().values()) {
					if (c.getHealthForSimulation() > 0) {
						survived++;
					}
//					////System.out.println(c.getID() + ": "
//							+ c.getHealthForSimulation() + " / "
//							+ c.getHealth());
				}
			}
		} catch (ConcurrentModificationException e) {
			e.printStackTrace();
		}

		System.out.println("survived: " + survived);
	}

	private static class SimulationResult {
		private int action; // standard: 0, build: 1, upgrade: 2, sell and
							// upgrade: 3
		private Cell cell;

		private String newTower;
		private String buildName;

		private int creepsSurvived = Integer.MAX_VALUE;
		
		private int totalHealth = Integer.MAX_VALUE;

		public SimulationResult withAction(int action) {
			this.action = action;
			return this;
		}

		public SimulationResult withCell(Cell cell) {
			this.cell = cell;
			return this;
		}

		public SimulationResult withNewTower(String newTower) {
			this.newTower = newTower;
			this.buildName = newTower.replaceAll("tower", "");
			return this;
		}
	}
	
	private static double getDistance(Cell c1, Cell c2) {
		return Math.pow(c1.getX() - c2.getX(), 2) + Math.pow(c1.getY() - c2.getY(), 2);
	}
}
